﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;

using BizElements.Core;

namespace BizElements.BusinessLayer
{
    /// <summary>
    /// Simple <see cref="IActor"/> implementation which maps all <see cref="IActor"/> members to class fields.
    /// </summary>
    /// <remarks>This class is not suitable for often remoting since it's serialization footprint may be very large.
    /// Recommendation: use the class only as a data-transfer object (DTO) and cache the data it contains on all servers.
    /// </remarks>
    [Serializable]
    public class Actor : IActor
    {
        HashSet<string> functionIds = new HashSet<string>();
        IPropertyCollection<string> extendedProperties = new PropertyCollection<string>(true, new Dictionary<string, string>());

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of <b>Actor</b> class.
        /// </summary>
        public Actor()
        {
        }

        /// <summary>
        /// Initializes a new instance of <b>Actor</b> class.
        /// </summary>
        /// <param name="id">The value, tipically an integer or a string, which identifies the current actor and it's 
        /// <b>position</b> in the system/organization. If <b>null</b> then <b>name</b> must be provided.</param>
        public Actor(object id)
        {
            this.Id = id;
        }

        /// <summary>
        /// Initializes a new instance of <b>Actor</b> class.
        /// </summary>
        /// <param name="id">The value, tipically an integer, guid or a string, which identifies the current actor and it's 
        /// <b>position</b> in the system/organization. If <b>null</b> then <b>name</b> must be provided.</param>
        /// <param name="name">The actor used to gain access to the system. If <b>null</b> then <b>id</b> must be provided.</param>
        public Actor(object id, string name)
        {
            this.Id = id;
            this.Name = name;
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets or sets the value, tipically an integer or a string, which identifies the current actor and it's 
        /// <b>position</b> in the system/organization.
        /// </summary>
        /// <remarks>Required to implement horizontal security. Data model must link actors and data they can access.</remarks>
        public object Id { get; set; }

        /// <summary>
        /// Gets or sets the name the actor used to gain access to the system.
        /// </summary>
        /// <remarks>May be used to implement horizontal security. However, <see cref="Id"/> should be prefered
        /// since in many systems username may be changed.</remarks>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the ID of the actor's primary unit. Tipically an integer or a string; <b>null</b> if primary unit is not defined for the current actor.
        /// </summary>
        /// <remarks><para>Even though not typical, actor may be associated with more than one unit. This property specifies which of 
        /// them is considered to be primary.</para>
        /// <para>Often used to enforce horizontal security. Sometimes all actors of a unit have the permission 
        /// to work on the same set of data.</para></remarks>
        public object PrimaryUnit { get; set; }

        /// <summary>Returns an array that contains a single value: <see cref="PrimaryUnit"/>.
        /// Current implementation doesn't support multiple units but this may change in the future.</summary>
        public object[] AllUnits
        {
            get { return new object[] { PrimaryUnit }; }
        }

        /// <summary>Gets or sets the collection of customized information associated with the actor, such as full name, business unit name and similar.</summary>
        /// <value>A IPropertyCollection with all custom information.</value>
        public IPropertyCollection<string> ExtendedProperties
        {
            get { return this.extendedProperties; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value", "Null is not allowed.");

                this.extendedProperties = value;
            }
        }

        /// <summary>Gets or sets the ID of the organization to which the actor belongs. Required for multi-tenant installations. Tipically an integer or GUID.</summary>
        public object Organization { get; set; }

        /// <summary>Gets or sets optional Id of actor's currently active session.</summary>
        public object SessionId { get; set; }

        #endregion

        #region Methods.

        /// <summary>
        /// Determines whether the current actor has permission to initiate the specified action.
        /// </summary>
        /// <param name="actionId">The ID of the action that the actor is trying to initiate. Implementations should 
        /// return <b>false</b> if the given ID is of the wrong type (eg. <b>string</b> instead of <b>int</b>.</param>
        /// <returns><b>True</b> it the current actor has permission to initiate the specified action; <b>false</b> otherwise.</returns>
        /// <remarks>Required to implement vertical security. Vertical security checks should be processed before,
        /// often more complex, horizontal security.</remarks>
        public virtual bool HasPermission(object actionId)
        {
            return this.functionIds.Contains(Convert.ToString(actionId));
        }

        /// <summary>
        /// Determines whether the current actor belongs to the specified unit.
        /// </summary>
        /// <param name="unit">The unit for which to check membership. Implementations should return <b>false</b> 
        /// if the given value is of the wrong type (eg. <b>string</b> instead of <b>int</b>.</param>
        /// <returns><b>True</b> it the current actor is a member of the specified unit; <b>false</b> otherwise.</returns>
        /// <remarks>Often used to enforce horizontal security. Sometimes all actors of a unit have the permission 
        /// to work on the same set of data.</remarks>
        public virtual bool IsInUnit(object unit)
        {
            return EqualValues(this.PrimaryUnit, unit);
        }

        /// <summary>Sets functions IDs for which the actor has permission to execute/access.</summary>
        /// <param name="functionIds">Functions IDs. Typically a collection of integers, guids or strings.</param>
        public void SetPermissionsForFunctions(IEnumerable functionIds)
        {
            this.functionIds.Clear();
            foreach (object id in functionIds)
                this.functionIds.Add(Convert.ToString(id));
        }

        #endregion

        private static bool EqualValues(object x, object y)
        {
            return Convert.ToString(x, CultureInfo.InvariantCulture) == Convert.ToString(y, CultureInfo.InvariantCulture);
        }
    }
}
